/**
 * Copyright &copy; 2012-2014 <a href="https://github.com/thinkgem/jeesite">JeeSite</a> All rights reserved.
 */
package genesis.familytree.server.common.web;

import genesis.familytree.server.common.Constants;
import genesis.familytree.server.common.JsonResult;
import genesis.familytree.server.common.beanvalidator.BeanValidators;
import genesis.familytree.server.common.mapper.JsonMapper;
import genesis.familytree.server.common.utils.CaptchaUtils;
import genesis.familytree.server.common.utils.DateUtils;
import genesis.familytree.server.common.utils.StringUtils;
import genesis.familytree.server.modules.common.service.IParamService;
import org.apache.commons.lang3.StringEscapeUtils;
import org.apache.shiro.authc.AuthenticationException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.ui.Model;
import org.springframework.validation.BindException;
import org.springframework.validation.BindingResult;
import org.springframework.web.bind.WebDataBinder;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.InitBinder;
import org.springframework.web.servlet.mvc.support.RedirectAttributes;

import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.validation.ConstraintViolationException;
import javax.validation.ValidationException;
import javax.validation.Validator;
import java.beans.PropertyEditorSupport;
import java.io.IOException;
import java.text.MessageFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;

import static genesis.familytree.server.common.utils.CaptchaUtils.PARAM_NAME_DEFAULT_CAPTCHA;

/**
 * 控制器支持类
 *
 * @author ThinkGem
 * @version 2013-3-23
 */
public abstract class BaseController {

    public enum MessageType {ERROR, SUCCESS, WARN, INFO}

    public static final String PARAM_NAME_CAPTCHA = PARAM_NAME_DEFAULT_CAPTCHA;

    @Resource
    protected IParamService paramService;

    /**
     * 日志对象
     */
    protected Logger logger = LoggerFactory.getLogger(getClass());

    /**
     * 管理基础路径
     */
    @Value("${adminPath}")
    protected String adminPath;

    /**
     * 验证Bean实例对象
     */
    @Autowired
    protected Validator validator;

    /**
     * 判断是否全局开启验证码
     *
     * @return true | false
     */
    protected abstract boolean isCaptchaEnabled();

    /**
     * 校验“校验验证码”
     *
     * @param request Request
     * @param result json 返回值
     *
     * @return 如果是 true 则校验通过，或不需要校验
     */
    protected boolean checkCaptcha(HttpServletRequest request, JsonResult result) {
        return checkCaptcha(request, result, true);
    }

    /**
     * 校验“校验验证码”
     *
     * @param request Request
     * @param result json 返回值
     * @param required 是否必须，true 直接检验验证码不需要通过系统配置来决定
     *
     * @return  如果是 true 则校验通过，或不需要校验
     */
    protected boolean checkCaptcha(HttpServletRequest request, JsonResult result, boolean required) {
        String url = request.getRequestURI();
        String[] keys = CaptchaUtils.buildKeysByRequestUrl(url);

        boolean must = false;
        if (required) {
            must = true;
        }else {
            if (isCaptchaEnabled()) {
                must = true;
            }else {
                String paramCode = MessageFormat.format("CAPTCHA_URL_{0}_ENABLED", StringUtils.join(keys, "_")).toUpperCase();
                must = this.paramService.getBoolean(paramCode, false);
            }
        }

        if (must) {


            String captcha = request.getParameter(PARAM_NAME_CAPTCHA);
            String captcha2 = CaptchaUtils.getCaptchaInSession(request.getSession(), keys);

            if (StringUtils.isBlank(captcha)
                    || StringUtils.isBlank(captcha2)) {
                result.showCaptcha();
                return false;
            }

            if (!StringUtils.equals(captcha, captcha2)) {
                result.showVerifyCode("验证码不正确");
                return false;
            }
        }

        return true;
    }

    /**
     * 服务端参数有效性验证
     *
     * @return 验证成功：返回true；
     */
    protected boolean beanValidator(JsonResult result, BindingResult bindingResult) {
        if (bindingResult.hasErrors()) {
            result.error(bindingResult);
            return false;
        }
        return true;
    }


    /**
     * 服务端参数有效性验证
     *
     * @param object 验证的实体对象
     * @param groups 验证组
     * @return 验证成功：返回true；严重失败：将错误信息添加到 message 中
     */
    protected boolean beanValidator(Model model, Object object, Class<?>... groups) {
        try {
            BeanValidators.validateWithException(validator, object, groups);
        } catch (ConstraintViolationException ex) {
            List<String> list = BeanValidators.extractPropertyAndMessageAsList(ex, ": ");
            for (String s : list) {
                addErrorMessage(model, s);
            }
            return false;
        }
        return true;
    }

    /**
     * 服务端参数有效性验证
     *
     * @param object 验证的实体对象
     * @param groups 验证组
     * @return 验证成功：返回true；严重失败：将错误信息添加到 flash message 中
     */
    protected boolean beanValidator(RedirectAttributes redirectAttributes, Object object, Class<?>... groups) {
        try {
            BeanValidators.validateWithException(validator, object, groups);
        } catch (ConstraintViolationException ex) {
            List<String> list = BeanValidators.extractPropertyAndMessageAsList(ex, ": ");
            for (String s : list) {
                addErrorMessage(redirectAttributes, s);
            }
            return false;
        }
        return true;
    }

    /**
     * 服务端参数有效性验证
     *
     * @param object 验证的实体对象
     * @param groups 验证组，不传入此参数时，同@Valid注解验证
     * @return 验证成功：继续执行；验证失败：抛出异常跳转400页面。
     */
    protected void beanValidator(Object object, Class<?>... groups) {
        BeanValidators.validateWithException(validator, object, groups);
    }

    /**
     * 添加Model消息
     *
     * @param messages
     */
    protected BaseController addMessage(MessageType messageType, Model model, String messages) {
        Object obj = model.asMap().get("messages");
        List<Message> msgs = null;
        if (null == obj || !(obj instanceof List)) {
            msgs = new ArrayList<>();
            model.addAttribute("messages", msgs);
        } else {
            msgs = (List<Message>) obj;
        }
        msgs.add(new Message(messageType.toString().toLowerCase(), messages));
        return this;
    }

    protected BaseController addInfoMessage(Model model, String message) {
        return addMessage(MessageType.INFO, model, message);
    }

    protected BaseController addErrorMessage(Model model, String message) {
        return addMessage(MessageType.ERROR, model, message);
    }

    protected BaseController addSuccessMessage(Model model, String message) {
        return addMessage(MessageType.SUCCESS, model, message);
    }

    protected BaseController addWarnMessage(Model model, String message) {
        return addMessage(MessageType.WARN, model, message);
    }


    /**
     * 添加Flash消息
     *
     * @param message
     */
    protected BaseController addMessage(MessageType messageType, RedirectAttributes redirectAttributes, String message) {
//		StringBuilder sb = new StringBuilder();
//		for (String message : messages){
//			sb.append(message).append(messages.length>1?"<br/>":"");
//		}
//		redirectAttributes.addFlashAttribute("message", sb.toString());

        Object obj = redirectAttributes.asMap().get("messages");
        List<Message> msgs = null;
        if (null == obj || !(obj instanceof List)) {
            msgs = new ArrayList<>();
            redirectAttributes.addFlashAttribute("messages", msgs);
        } else {
            msgs = (List<Message>) obj;
        }
        msgs.add(new Message(messageType.toString().toLowerCase(), message));

        return this;
    }

    protected BaseController addInfoMessage(RedirectAttributes redirectAttributes, String message) {
        return addMessage(MessageType.INFO, redirectAttributes, message);
    }

    protected BaseController addErrorMessage(RedirectAttributes redirectAttributes, String message) {
        return addMessage(MessageType.ERROR, redirectAttributes, message);
    }

    protected BaseController addSuccessMessage(RedirectAttributes redirectAttributes, String message) {
        return addMessage(MessageType.SUCCESS, redirectAttributes, message);
    }

    protected BaseController addWarnMessage(RedirectAttributes redirectAttributes, String message) {
        return addMessage(MessageType.WARN, redirectAttributes, message);
    }

    /**
     * 客户端返回JSON字符串
     *
     * @param response
     * @param object
     * @return
     */
    protected String renderString(HttpServletResponse response, Object object) {
        return renderString(response, JsonMapper.toJsonString(object), "application/json");
    }

    /**
     * 客户端返回字符串
     *
     * @param response
     * @param string
     * @return
     */
    protected String renderString(HttpServletResponse response, String string, String type) {
        try {
            response.reset();
            response.setContentType(type);
            response.setCharacterEncoding("utf-8");
            response.getWriter().print(string);
            return null;
        } catch (IOException e) {
            return null;
        }
    }

    /**
     * 参数绑定异常
     */
    @ExceptionHandler({BindException.class, ConstraintViolationException.class, ValidationException.class})
    public String bindException() {
        return "error/400";
    }

    /**
     * 授权登录异常
     */
    @ExceptionHandler({AuthenticationException.class})
    public String authenticationException() {
        return "error/403";
    }

    /**
     * 初始化数据绑定
     * 1. 将所有传递进来的String进行HTML编码，防止XSS攻击
     * 2. 将字段中Date类型转换为String类型
     */
    @InitBinder
    protected void initBinder(WebDataBinder binder) {
        // String类型转换，将所有传递进来的String进行HTML编码，防止XSS攻击
        binder.registerCustomEditor(String.class, new PropertyEditorSupport() {
            @Override
            public void setAsText(String text) {
                setValue(text == null ? null : StringEscapeUtils.escapeHtml4(text.trim()));
            }

            @Override
            public String getAsText() {
                Object value = getValue();
                return value != null ? value.toString() : "";
            }
        });

        // Date 类型转换
        binder.registerCustomEditor(Date.class, new PropertyEditorSupport() {
            @Override
            public void setAsText(String text) {
                setValue(DateUtils.parseDate(text));
            }
//			@Override
//			public String getAsText() {
//				Object value = getValue();
//				return value != null ? DateUtils.formatDateTime((Date)value) : "";
//			}
        });
    }

    public static class Message implements java.io.Serializable{
        public String type;
        public String message;

        public Message(String type, String message) {
            this.type = type;
            this.message = message;
        }

        public String getType() {
            return type;
        }

        public void setType(String type) {
            this.type = type;
        }

        public String getMessage() {
            return message;
        }

        public void setMessage(String message) {
            this.message = message;
        }

        @Override
        public String toString() {
            return message + "<br/>";
        }
    }
}
